package cn.toolck.springmvc.spring.factory;

import cn.toolck.springmvc.spring.annotation.MyAutowired;
import cn.toolck.springmvc.spring.annotation.MyController;
import cn.toolck.springmvc.spring.annotation.MyService;
import cn.toolck.springmvc.spring.bean.BeanDefinition;
import cn.toolck.springmvc.spring.servlet.MyDispatcherServlet;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @ClassName BeanFactory
 * @Description TODO
 * @Author Toolck Rin
 * @Date 2021/2/24 15:33
 * @Version 1.0
 **/
public class BeanFactory {
    private final String PACKAGE_REAL_PATH = "packageRealPath";
    private final String PACKAGE = "package";
    // 缓存扫描到的类的全限定类名
    private List<String> classNames = new ArrayList<>();

    private Properties properties = new Properties();

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    private Map<Class, String> typeToBeanName = new HashMap<>();

    // ioc容器
    private Map<String, Object> beanMap = new HashMap<>();
    private Map<String, Object> earlyBeanMap = new HashMap<>();

    public void refesh(String property) {
        scanPackage(property);
        creatAllBean();
    }

    private void creatAllBean() {
        beanDefinitionMap.forEach((key, beanDefinition) -> {
            getBean(key);
        });
    }

    public Object getBean(Class aClass) {
        return getBean(typeToBeanName.get(aClass));
    }

    public Object getBean(String beanName) {
        return doGetBean(beanName);
    }

    private Object doGetBean(String beanName) {
        Object bean = beanMap.get(beanName);
        if (bean != null) {
            return bean;
        }
        bean = earlyBeanMap.get(beanName);
        if (bean != null) {
            return bean;
        }
        bean = creatBean(beanName);
        addBean(beanName, bean);
        return bean;
    }

    private void addBean(String beanName, Object bean) {
        earlyBeanMap.remove(beanName);
        beanMap.put(beanName, bean);
    }

    private Object creatBean(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        Class classType = beanDefinition.getClassType();
        try {
            Object bean = classType.getConstructor().newInstance();
            List<Field> dependencies = beanDefinition.getDependencies();
            if (!CollectionUtils.isEmpty(dependencies)) {
                earlyBeanMap.put(beanName, bean);
                for (Field dependency : dependencies) {
                    dependency.setAccessible(true);
                    String dependencyName = dependency.getAnnotation(MyAutowired.class).value();
                    if (!StringUtils.isEmpty(dependencyName)) {
                        dependency.set(bean, getBean(dependencyName));
                    } else {
                        dependency.set(bean, getBean(dependency.getType()));
                    }
                }
            }
            return bean;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private void scanPackage(String property) {
        String packagePath = property.replace(".", File.separator);
        properties.put(PACKAGE, property);
        String rootPath = MyDispatcherServlet.class.getClassLoader().getResource("").getPath();
        String packageRealPath = rootPath + packagePath;
        properties.put(PACKAGE_REAL_PATH, packageRealPath);
        File file = new File(packageRealPath);
        doScan(file);
    }

    public void doScan(File file) {
        if (!file.isDirectory()) {
            String path = file.getPath();
            String realPath = path.substring(properties.getProperty(PACKAGE_REAL_PATH).length());
            String className = properties.getProperty(PACKAGE) + "." + realPath.replace(File.separator, ".").replace(".class", "");
            creatBeanDefinition(className);
            return;
        }
        File[] files = file.listFiles(pathname -> pathname.getName().endsWith(".class") || pathname.isDirectory());
        for (int i = 0; i < files.length; i++) {
            doScan(files[i]);
        }
    }

    private void creatBeanDefinition(String className) {
        try {
            Class<?> aClass = Class.forName(className);
            String beanName;
            String type;
            if (aClass.isAnnotationPresent(MyController.class)) {
                beanName = aClass.getAnnotation(MyController.class).value();
                type = MyController.class.getName();
            } else if (aClass.isAnnotationPresent(MyService.class)) {
                beanName = aClass.getAnnotation(MyService.class).value();
                type = MyService.class.getName();
            } else {
                return;
            }
            doCreatBeanDefinition(beanName, type, aClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void doCreatBeanDefinition(String beanName, String type, Class aClass) {
        if (StringUtils.isEmpty(beanName)) {
            beanName = lowFirstChar(aClass.getSimpleName());
        }
        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setClassType(aClass);
        beanDefinition.setBeanName(beanName);
        beanDefinition.setBeanType(type);
        Field[] declaredFields = aClass.getDeclaredFields();
        List<Field> dependencies = beanDefinition.getDependencies();
        for (Field field : declaredFields) {
            if (field.isAnnotationPresent(MyAutowired.class)) {
                dependencies.add(field);
            }
        }
        beanDefinitionMap.put(beanName, beanDefinition);
        Class[] interfaces = aClass.getInterfaces();
        for (Class anInterface : interfaces) {
            if (typeToBeanName.containsKey(anInterface)) {
                throw new RuntimeException("存在此类型的bean");
            }
            typeToBeanName.put(anInterface, beanName);
        }
        typeToBeanName.put(aClass, beanName);
    }

    private String lowFirstChar(String str) {
        StringBuffer stringBuffer = new StringBuffer(str);
        char first = stringBuffer.charAt(0);
        if (first < 'Z' && first > 'A') {
            first = (char) (first - 'A' + 'a');
            stringBuffer.setCharAt(0, first);
        }
        return stringBuffer.toString();
    }


    public Set<String> getAllBeanName() {
        return beanDefinitionMap.keySet();
    }

    public BeanDefinition getBeanDefinition(String beanName) {
        return beanDefinitionMap.get(beanName);
    }
}
